package com.snack.common.utils;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Set;

@SuppressWarnings("unchecked")
public class ClassLoaderUtils {
	public static Iterator<URL> getResources(String resourceName, Class callingClass, boolean aggregate) throws IOException {
		AggregateIterator iterator = new AggregateIterator();

		iterator.addEnumeration(Thread.currentThread().getContextClassLoader().getResources(resourceName));

		if ((!iterator.hasNext()) || (aggregate)) {
			iterator.addEnumeration(ClassLoaderUtils.class.getClassLoader().getResources(resourceName));
		}

		if ((!iterator.hasNext()) || (aggregate)) {
			ClassLoader cl = callingClass.getClassLoader();

			if (cl != null) {
				iterator.addEnumeration(cl.getResources(resourceName));
			}
		}

		if ((!iterator.hasNext()) && (resourceName != null) && ((resourceName.length() == 0) || (resourceName.charAt(0) != '/'))) {
			return getResources('/' + resourceName, callingClass, aggregate);
		}

		return iterator;
	}

	public static URL getResource(String resourceName, Class callingClass) {
		URL url = Thread.currentThread().getContextClassLoader().getResource(resourceName);

		if (url == null) {
			url = ClassLoaderUtils.class.getClassLoader().getResource(resourceName);
		}

		if (url == null) {
			ClassLoader cl = callingClass.getClassLoader();

			if (cl != null) {
				url = cl.getResource(resourceName);
			}
		}

		if ((url == null) && (resourceName != null) && ((resourceName.length() == 0) || (resourceName.charAt(0) != '/'))) {
			return getResource('/' + resourceName, callingClass);
		}

		return url;
	}

	public static InputStream getResourceAsStream(String resourceName, Class callingClass) {
		URL url = getResource(resourceName, callingClass);
		try {
			return url != null ? url.openStream() : null;
		} catch (IOException e) {
		}
		return null;
	}

	public static Class loadClass(String className, Class callingClass) throws ClassNotFoundException {
		try {
			return Thread.currentThread().getContextClassLoader().loadClass(className);
		} catch (ClassNotFoundException e) {
			try {
				return Class.forName(className);
			} catch (ClassNotFoundException ex) {
				try {
					return ClassLoaderUtils.class.getClassLoader().loadClass(className);
				} catch (ClassNotFoundException exc) {
				}
			}
		}
		return callingClass.getClassLoader().loadClass(className);
	}

	static class AggregateIterator<E> implements Iterator<E> {
		LinkedList<Enumeration<E>> enums = new LinkedList();
		Enumeration<E> cur = null;
		E next = null;
		Set<E> loaded = new HashSet();

		public AggregateIterator<E> addEnumeration(Enumeration<E> e) {
			if (e.hasMoreElements()) {
				if (this.cur == null) {
					this.cur = e;
					this.next = e.nextElement();
					this.loaded.add(this.next);
				} else {
					this.enums.add(e);
				}
			}
			return this;
		}

		public boolean hasNext() {
			return this.next != null;
		}

		public E next() {
			if (this.next != null) {
				E prev = this.next;
				this.next = loadNext();
				return prev;
			}
			throw new NoSuchElementException();
		}

		private Enumeration<E> determineCurrentEnumeration() {
			if ((this.cur != null) && (!this.cur.hasMoreElements())) {
				if (this.enums.size() > 0)
					this.cur = ((Enumeration) this.enums.removeLast());
				else {
					this.cur = null;
				}
			}
			return this.cur;
		}

		private E loadNext() {
			if (determineCurrentEnumeration() != null) {
				E tmp = this.cur.nextElement();
				int loadedSize = this.loaded.size();
				while (this.loaded.contains(tmp)) {
					tmp = loadNext();
					if (tmp == null)
						break;
					if (this.loaded.size() > loadedSize) {
						break;
					}
				}
				if (tmp != null) {
					this.loaded.add(tmp);
				}
				return tmp;
			}
			return null;
		}

		public void remove() {
			throw new UnsupportedOperationException();
		}
	}

	public static ClassLoader getStandardClassLoader() {
		return Thread.currentThread().getContextClassLoader();
	}

	public static ClassLoader getFallbackClassLoader() {
		return ClassLoaderUtils.class.getClassLoader();
	}

	public static Object createNewInstance(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException
	{
		Class clazz;
		try {
			clazz = Class.forName(className, true, getStandardClassLoader());
		} catch (ClassNotFoundException e) {
			try {
				clazz = Class.forName(className, true, getFallbackClassLoader());
			} catch (ClassNotFoundException ex) {
				throw new ClassNotFoundException("Unable to load class " + className + ". Initial cause was " + e.getMessage(), e);
			}
		}
		Object newInstance;
		try {
			newInstance = clazz.newInstance();
		} catch (IllegalAccessException e) {
			throw e;
		} catch (InstantiationException e) {
			throw e;
		}

		return newInstance;
	}
}
